#include "np.h"
#include "packet.h"

typedef enum {
        Wait_for_call,
        Wait_for_ACK,
        Closing
} State;

extern int DEBUG_ON;    // print debug messages if set
static unsigned int     ReTxTimeout = 100;              // default timeout in msec
static char *localhost = "127.0.0.1";                   // default receiver hostname
static char *port = "5678";                                             // default receiver port

//Packet buffers
static Packet   sndPacket, rcvPacket;
/*
 * rdt3.0 sender
 */
int main( int argc, char **argv )
{
        struct sockaddr_in peer;
        int     fd, s;
        unsigned char sndSeq;
        Event   event;
        State   state;
        Packet  *sndpkt = &sndPacket;
        Packet *rcvpkt = &rcvPacket;
        char    *data = &(sndPacket.data[0]);
        char    *hostname, *service;
        int     opt;

        // Command line option processing
        while ( (opt = getopt(argc, argv, "dt:")) != -1) {
                switch (opt) {
                case 'd':
                        DEBUG_ON = 1;   // Debug mode
                        break;
                case 't':
                        ReTxTimeout = atoi(optarg);
                        break;
                default:
                        error(1, 0, "Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n", argv[0] );
                }
        }
        if (optind == argc-2) {
                hostname = argv[optind];
                service = argv[optind+1];
        } else if (optind == argc) {
                hostname = localhost;
                service = port;
        } else
                error(1, 0, "Usage: %s [-d] [-t timeout] [<receiver name or IP address> <port number>]\n", argv[0] );
        debug("To %s:%s\tTimeout = %dmsec\tDebugMode = %s\n", hostname, service, ReTxTimeout,
                        DEBUG_ON ? "on" : "off");

        set_address( hostname, service, &peer, "udp" ); // Set peer's IP and port

        if ( (s = socket( AF_INET, SOCK_DGRAM, 0 )) < 0)
                error(1, errno, "socket creation failed\n" );

        fd = fileno(stdin);

        // Init conditiion
        sndSeq = 0;
        state = Wait_for_call;

        // FSM
        for (;;) {
                int     datalen, rcvlen;

                event = getEvent(fd, s);
                // Actions taken by State and Event
                switch (state) {
                // Wait_for_call state
                case Wait_for_call:
                        switch (event) {
                        case RDT_SEND:
                                if ( fgets( data, MAX_DATA, stdin) != NULL ) {  // Get data from above

                                        datalen = sizeof(data);
                                        sndpkt = make_pkt(0, sndSeq, &rcvPacket, 0);
                                        udt_send(s, sndpkt, (struct sockaddr *)&data, datalen);
                                        state = Wait_for_ACK;
                                        start_timer(ReTxTimeout);
                                        /*
                                         * More codes here ...
                                         */
                                        continue;
                                } else {                                // EoF encountered. Close the session

                                        datalen = sizeof(data);
                                        sndpkt = make_pkt(END, sndSeq, &rcvPacket, 0);
                                        udt_send(s, sndpkt, (struct sockaddr *)&data, datalen);
                                        state = Closing;
                                        start_timer(ReTxTimeout);
                                        /*
                                         * More codes here ...
                                         */
                                }
                        default:
                                continue;                               // ignore the event
                        }
                //Wait_for_ACK state
                case Wait_for_ACK:
                        switch (event) {
                        case RDT_RCV:

                                stop_timer();
                                state = Wait_for_call;
                                /*
                                 * More codes here ...
                                 */
                                        continue;
                        case TIMEOUT:

                                stop_timer();
                                udt_send(s, sndpkt, (struct sockaddr *)&data, datalen);
                                start_timer(ReTxTimeout);
                                /*
                                 * More codes here ...
                                 */
                                continue;
                        default:
                                continue;
                        }
                // Closing state
                        // wait an ACK. Sender shall stop when anything hear from receiver
                case Closing:
                        switch (event) {
                        case RDT_RCV:

                                stop_timer();
                                state = Wait_for_call;
                                exit(0);
                                /*
                                 * More codes here ...
                                 */
                        case TIMEOUT:

                                stop_timer();
                                udt_send(s, sndpkt, (struct sockaddr *)&data, datalen);
                                start_timer(ReTxTimeout);
                                /*
                                 * More codes here ...
                                 */
                                continue;
                        default:
                                continue;
                        }       // End of inner switch
                }       // End of outer switch

        }   // End of for loop

}       // End of main

